#include "gl_header_parser.h"
#include <sstream>
#include <iostream>
#include <algorithm>
#include <cctype>

namespace{
	std::string toupper(std::string s){
		std::transform(s.begin(), s.end(), s.begin(), std::toupper);
		return s;
	}

	bool startswith(const std::string &s, const std::string &f){
		return s.substr(0, f.length()) == f;
	}

	bool contains(const std::string &s, const std::string &f){	
		return s.find(f) != std::string::npos;
	}
}

void gl_header_parser::dump_groups(std::ostream &out){
	out << "#define ALL_EXTENSIONS(NAME, FUNC, CONST, END) \\\n";
	for(auto ext = m_Extensions.begin(); ext != m_Extensions.end(); ext++){
		out << "\tEXTENSION_" << ext->first << "(NAME, FUNC, CONST, END) \\\n";
	}

	out << "\n";
	out << "#define ONLY_VERSION MAX_VERSION_EXTENSIONS\n"
		<< "#define VERSION_EXTENSIONS(NAME, FUNC, CONST, END) \\\n";

	std::vector<std::string> versions;

	for(auto ext = m_Extensions.begin(); ext != m_Extensions.end(); ext++){
		if(startswith(ext->first, "GL_VERSION")){
			out << "\tEXTENSION_" << ext->first << "(NAME, FUNC, CONST, END) \\\n";
			versions.push_back(ext->first);
		}
	}

	out << "\n";

	std::sort(versions.begin(), versions.end());

	for(size_t i = 0; i < versions.size(); i++){
		std::string s = "GL_VERSION_";
		std::string v = versions[i].substr(s.length());
		bool deprecated = contains(versions[i], "DEPRECATED");

		out << "#define VERSION_" << v << " VERSION_" << v << "_EXTENSIONS\n";
		out << "#define " << "VERSION_" << v << "_EXTENSIONS(NAME, FUNC, CONST, END) \\\n";
		for(size_t j = 0; j <= i; j++){
			if(deprecated || !contains(versions[j], "DEPRECATED")){
				out << "\tEXTENSION_" << versions[j] << "(NAME, FUNC, CONST, END) \\\n";
			}
		}
		out << "\n";
	}

	out << "#define ARB_EXTENSIONS(NAME, FUNC, CONST, END) \\\n";

	for(auto ext = m_Extensions.begin(); ext != m_Extensions.end(); ext++){
		if(startswith(ext->first, "GL_ARB")){
			out << "\tEXTENSION_" << ext->first << "(NAME, FUNC, CONST, END) \\\n";
		}
	}

	out << "\n";
	out << "#define EXT_EXTENSIONS(NAME, FUNC, CONST, END) \\\n";

	for(auto ext = m_Extensions.begin(); ext != m_Extensions.end(); ext++){
		if(startswith(ext->first, "GL_EXT")){
			out << "\tEXTENSION_" << ext->first << "(NAME, FUNC, CONST, END) \\\n";
		}
	}

	out << "\n";
	out << "#define IMPORTANT_EXTENSIONS(NAME, FUNC, CONST, END) \\\n"
		<< "\tMAX_VERSION_EXTENSIONS(NAME, FUNC, CONST, END) \\\n";

		for(auto ext = m_Extensions.begin(); ext != m_Extensions.end(); ext++){
		if(startswith(ext->first, "GL_EXT") || startswith(ext->first, "GL_ARB")){
			out << "\tEXTENSION_" << ext->first << "(NAME, FUNC, CONST, END) \\\n";
		}
	}

	out << "\n";
	out << "#define CRITICAL_EXTENSIONS(NAME, FUNC, CONST, END) \\\n"
		<< "\tMAX_VERSION_EXTENSIONS(NAME, FUNC, CONST, END) \\\n";

		for(auto ext = m_Extensions.begin(); ext != m_Extensions.end(); ext++){
		if(startswith(ext->first, "GL_ARB")){
			out << "\tEXTENSION_" << ext->first << "(NAME, FUNC, CONST, END) \\\n";
		}
	}
}

void gl_header_parser::dump(std::ostream &out){
	out << m_Prepend;

	out << "#define NAME_EMPTY(x)\n"
		<< "#define FUNC_EMPTY(x, y, z, w, t)\n"
		<< "#define END_EMPTY()\n"
		<< "#define CONST_EMPTY(x, y, z)\n\n";

	for(auto ext = m_Extensions.begin(); ext != m_Extensions.end(); ext++){
		out
			<< "#ifndef " << ext->first << "\n"
			<< "#define " << ext->first << " 1\n"
			<< "#define EXTENSION_" << ext->first << "(NAME, FUNC, CONST, END) \\\n"
			<< "\tNAME(" << ext->first << ") \\\n";

		auto i = ext->second;

		for(auto c = i.m_Constants.begin(); c != i.m_Constants.end(); c++){
			std::string value = c->m_Value;
			std::string type = c->m_Type;

			if(!c->m_IsLiteral){
				auto resolved = m_AllConstants.find(value);
				if(resolved != m_AllConstants.end()){
					value = resolved->second.m_Value;
					type = resolved->second.m_Type;
				}else type = "int";
			}

			out << "\tCONST(" << type << ", " << c->m_Name << ", " << value << ") \\\n";
		}

		for(auto f = i.m_Functions.begin(); f != i.m_Functions.end(); f++){
			out << "\tFUNC(" 
				<< f->m_ReturnType << ", " 
				<< f->m_Name << ", " 
				<< toupper(f->m_Name) << ", "
				<< "(";

			for(auto p = f->m_Parameters.begin(); p != f->m_Parameters.end(); p++){
				if(p != f->m_Parameters.end() - 1){
					out << p->m_Full << ", ";
				}else{
					out << p->m_Full;
				}
			}

			out << "), (";

			for(auto p = f->m_Parameters.begin(); p != f->m_Parameters.end(); p++){
				if(p != f->m_Parameters.end() - 1){
					out << p->m_Name << ", ";
				}else{
					out << p->m_Name;
				}
			}

			out << ")) \\\n";
		}

		out
			<< "\tEND()\n"
			<< "#else\n"
			<< "#define EXTENSION_" << ext->first << "(NAME, FUNC, CONST, END)\n"
			<< "#endif\n\n";
	}

	dump_groups(out);
}

void gl_header_parser::parse(){
	// skip past init crap
	size_t start = -1, end = -1;

	try{
		while(true){
			move();
			if(m_Look.tag == kComment && m_Look.lexeme.find("******") != std::string::npos)
				break;
		}

		while(m_Look.tag != kIfndef)
			move();

//		std::string skipped = m_Lexer.get_raw(0, m_Look.pos);

		while(m_Look.tag != kComment){
			match(kIfndef);
			std::string category = m_Look.lexeme;
		
			info &i = m_Extensions[category];
			match(kIdentifier);

			parse_const_cat(i);

			match(kEndif);
		}

		start = m_Look.pos;
		end = -1;
		bool first = true;

		// parse function prototypes
		while(true){
			std::string category = "";

			if(
				silent_match(kIfndef) &&
				silent_match(kIdentifier, category) &&
				silent_match(kDefine) && 
				silent_match(kIdentifier) &&
				silent_match(kNumeralDec) &&
				silent_match(kIfdef))
			{
				if(first){
					first = false;
				}

				if(contains(m_Look.lexeme, "_PROTOTYPES")){
					move();
				
					info &i = m_Extensions[category];
					parse_functions(i);

					// skip past typedefs
					while(m_Look.tag != kEndif)  move();
				}
			}else{
				move();
				if(first){
					end = m_Look.pos;					
				}
			}
		}

	}catch(lexer_eof &eof){
		m_Prepend = m_Lexer.get_raw(start, end);
		std::cout << "Done" << std::endl;
	}
}

void gl_header_parser::parse_functions(info &i){
	while(m_Look.tag != kEndif){

		function f;

		// parse function definitions
		match(kIdentifier); // GLAPI

		f.m_ReturnType = "";
		std::string last = "";
		while(m_Look.tag != kOpenParentheses){
			if(m_Look.lexeme != "APIENTRY" && m_Look.lexeme != "WINAPI"){
				f.m_ReturnType += m_Look.lexeme + " ";
				last = m_Look.lexeme;
			}
			move();
		}

		// remove function name
		f.m_ReturnType = f.m_ReturnType.substr(0, f.m_ReturnType.length() - (last.length() + 1));

		f.m_Name = last;
		match(kOpenParentheses);

		// match args
		while(m_Look.tag != kCloseParentheses){
			parameter p;
			p.m_Full = "";
			p.m_Name = "";
			while(m_Look.tag != kComma && m_Look.tag != kCloseParentheses){
				p.m_Full += m_Look.lexeme + " ";
				p.m_Name = m_Look.lexeme == "void" ? "" : m_Look.lexeme;
				move();
			}

			if(m_Look.tag != kCloseParentheses) move();
			f.m_Parameters.push_back(p);
		}

		i.m_Functions.push_back(f);

		match(kCloseParentheses);
		match(kEndOfStatement);
	}
}

bool gl_header_parser::silent_match(simple_c_tokens tag, std::string &outlexeme){
	if(m_Look.tag == tag){
		outlexeme = m_Look.lexeme;
		move();
		return true;
	}

	return false;
}

bool gl_header_parser::silent_match(simple_c_tokens tag){
	if(m_Look.tag == tag){
		move();
		return true;
	}

	return false;
}

void gl_header_parser::parse_const_cat(info &i){
	while(m_Look.tag != kEndif){
		switch(m_Look.tag) {
			case kDefine:
			{
				match(kDefine);
				std::string name = m_Look.lexeme;
				match(kIdentifier);
				std::string value = m_Look.lexeme;
				std::string type = "undefined";
				bool isliteral = m_Look.tag != kIdentifier;

				if(!isliteral){
					match(kIdentifier);
				}else{
					type = get_type();
					match_numeral();
				}

				constant c = {name, value, type, isliteral};
				m_AllConstants[name] = c;
				i.m_Constants.push_back(c);
				break;
			}
			case kComment:
			{
				std::string comment = m_Look.lexeme;

				match(kComment);
				// TODO: mark reference
				if(comment.find("reuse") != std::string::npos || comment.find("Reuse") != std::string::npos){
				}
			}
		}
	}
}

std::string gl_header_parser::get_type(){
	// assumes m_Look.tag is of numeral kind
	std::string type = "";

	for(std::string::size_type i = m_Look.lexeme.size() - 1; i; i--){
		if(m_Look.lexeme[i] == 'u' || m_Look.lexeme[i] == 'U'){
			type += "unsigned ";
		}

		if(m_Look.lexeme[i] == 'l' || m_Look.lexeme[i] == 'L'){
			type += "long ";
		}
	}

	if(type.empty()) type = "int";

	return type;
}

void gl_header_parser::match_numeral(){
	if(m_Look.tag == kNumeralOct || m_Look.tag == kNumeralHex || m_Look.tag == kNumeralDec){
		move();
	}else{
		unexpected(kNumeralDec, m_Look.tag);
	}
}

void gl_header_parser::match(simple_c_tokens tag){
	if(m_Look.tag == tag){
		move();
	}else{
		unexpected(tag, m_Look.tag);
	}
}

namespace {
	std::string tag_to_string(simple_c_tokens tag){
		std::stringstream ss;
		ss << tag;
		return ss.str();
	}
}

void gl_header_parser::unexpected(simple_c_tokens a_Expected, simple_c_tokens a_Current){
	std::string l_Current = tag_to_string(a_Current);

	if(!l_Current.empty()){
		error(
			  "unexpected token '" 
			+ l_Current 
			+ "', expecting '" 
			+ tag_to_string(a_Expected)
			+ "'"
		);
	}else{
		error(
			  "expecting token '"
			+ tag_to_string(a_Expected)
			+ "'"
		);
	}
}

void gl_header_parser::error(const std::string &a_Error){
	std::stringstream l_Stream;
	l_Stream
		<< "Error: " << a_Error 
		<< " on line " << m_Lexer.line() << ".";

	throw gl_header_syntax_error(l_Stream.str());
}


void gl_header_parser::move(){
	m_Look = m_Lexer.scan();
}